package watcher

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	//"time"

	"github.com/shirou/gopsutil/process"

	"../pb"
)

var last map[int]*pb.Status = make(map[int]*pb.Status)

var TCPSTATUS = map[string]string{
	"01": "established",
	"02": "syn_sent",
	"03": "syn_recv",
	"04": "fin_wait1",
	"05": "fin_wait2",
	"06": "time_wait",
	"07": "close",
	"08": "close_wait",
	"09": "last_ack",
	"0A": "listen",
	"0B": "closing",
	"0C": "max_states",
}

func IsNull(t *pb.Status) bool {
	return t == nil || t.ProcCount == 0
}
func Formarted(t *pb.Status) []string {
	if t != nil {
		return []string{t.String()}
	}
	return []string{"N/a"}
}

func Addto(t *pb.Status, o *pb.Status) {
	if o == nil {
		return
	}
	t.CuTime = t.CuTime + o.CuTime
	t.CsTime = t.CsTime + o.CsTime
	t.STime = t.STime + o.STime
	t.UTime = t.UTime + o.UTime
	t.CpuUsage = t.CpuUsage + o.CpuUsage
	//	t.BlkioTicks = t.BlkioTicks + o.BlkioTicks
	t.MemSize = t.MemSize + o.MemSize
	t.ThreadCount = t.ThreadCount + o.ThreadCount
	t.ProcCount = t.ProcCount + o.ProcCount
	t.ReadBytes = t.ReadBytes + o.ReadBytes
	t.WriteBytes = t.WriteBytes + o.WriteBytes
	t.ReadCount = t.ReadCount + o.ReadCount
	t.WriteCount = t.WriteCount + o.WriteCount
	t.BytesSent = t.BytesSent + o.BytesSent
	t.BytesRecv = t.BytesRecv + o.BytesRecv
	t.PacketsSent = t.PacketsSent + o.PacketsSent
	t.PacketsRecv = t.PacketsRecv + o.PacketsRecv
	if t.TcpCount == nil {
		t.TcpCount = make(map[string]int32)
	}
	for k, v := range o.TcpCount {
		_, ok := t.TcpCount[k]
		if ok {
			t.TcpCount[k] += v
		} else {
			t.TcpCount[k] = v
		}
	}
}

var FormatError = fmt.Errorf("Parse format error")

//const HZ int64 = 1000

func ParseStatus(s []byte) (*pb.Status, error) {
	ret := &pb.Status{}
	lines := strings.Split(string(s), " ")
	if len(lines) < 42 {
		return nil, FormatError
	}
	if c, err := strconv.Atoi(lines[19]); err != nil {
		return nil, FormatError
	} else {
		ret.ThreadCount = int32(c)
	}
	var i int64
	var err error
	if i, err = strconv.ParseInt(lines[13], 0, 64); err != nil {
		return nil, FormatError
	}
	ret.UTime = i // HZ

	if i, err = strconv.ParseInt(lines[14], 0, 64); err != nil {
		return nil, FormatError
	}
	ret.STime = i // HZ

	if i, err = strconv.ParseInt(lines[15], 0, 64); err != nil {
		return nil, FormatError
	}
	ret.CuTime = i // HZ

	if i, err = strconv.ParseInt(lines[16], 0, 64); err != nil {
		return nil, FormatError
	}
	ret.CsTime = i // HZ

	//	if i, err = strconv.ParseInt(lines[22], 0, 64); err != nil {
	//		return nil, FormatError
	//	}
	//	ret.MemSize = i

	//	if i, err = strconv.ParseInt(lines[41], 0, 64); err != nil {
	//		return nil, FormatError
	//	}
	//	ret.BlkioTicks = i / HZ
	return ret, nil
}

func ProcCounterMinus(last, now *pb.Status) *pb.Status {
	ret := &pb.Status{}
	if last == nil {
		return ret
	}
	ret.ProcType = now.ProcType
	ret.ProcCount = now.ProcCount
	ret.ThreadCount = now.ThreadCount
	ret.CuTime = now.CuTime - last.CuTime
	ret.CsTime = now.CsTime - last.CsTime
	ret.STime = now.STime - last.STime
	ret.UTime = now.UTime - last.UTime
	ret.CheckTime = now.CheckTime - last.CheckTime
	ret.MemSize = now.MemSize
	ret.ReadCount = now.ReadCount - last.ReadCount
	ret.WriteCount = now.WriteCount - last.WriteCount
	ret.ReadBytes = now.ReadBytes - last.ReadBytes
	ret.WriteBytes = now.WriteBytes - last.WriteBytes
	ret.BytesSent = now.BytesSent - last.BytesSent
	ret.BytesRecv = now.BytesRecv - last.BytesRecv
	ret.PacketsSent = now.PacketsSent - last.PacketsSent
	ret.PacketsRecv = now.PacketsRecv - last.PacketsRecv
	ret.TcpCount = now.TcpCount
	return ret
}

func GetStatus(pid int, port string) (*pb.Status, error) { //user state ctime
	if pid <= 1 {
		return &pb.Status{}, fmt.Errorf("pid is 0 or 1")
	}
	//cpu
	b, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
	if err != nil {
		return nil, err
	}
	s, err := ParseStatus(b)
	s.ProcCount = 1
	if err != nil {
		return nil, err
	}
	//s.CheckTime = time.Now().UnixNano() / FT
	cmdstr := `awk '{if ($1 == "cpu") {sum = $2 + $3 + $4 + $5 + $6 + $7 + $8 + $9 + $10 + $11;print sum}}' /proc/stat`
	cmdx := exec.Command(`/bin/sh`, `-c`, cmdstr)
	outx, _ := cmdx.CombinedOutput()
	cmdoutx := string(outx)
	CpuTime, _ := strconv.ParseInt(strings.TrimSpace(cmdoutx), 0, 64)
	s.CheckTime = CpuTime

	p, err := process.NewProcess(int32(pid))
	if err != nil {
		return nil, err
	}
	//memory
	ms, err := p.MemoryInfo()
	s.MemSize = int64(ms.RSS)

	//netIO
	if port == "" {
		s.BytesRecv = 0
		s.BytesSent = 0
		s.PacketsRecv = 0
		s.PacketsSent = 0
	} else {
		ports := strings.Split(port, `,`)
		for _, p := range ports {
			cmd := exec.Command(`iptables`, `-C`, `OUTPUT`, `-p`, `tcp`, `--sport`, p)
			err = cmd.Run()
			if err != nil {
				cmd = exec.Command(`iptables`, `-I`, `OUTPUT`, `-p`, `tcp`, `--sport`, p)
				cmd.Run()
			}

			cmd = exec.Command(`iptables`, `-C`, `INPUT`, `-p`, `tcp`, `--dport`, p)
			err = cmd.Run()
			if err != nil {
				cmd = exec.Command(`iptables`, `-I`, `INPUT`, `-p`, `tcp`, `--dport`, p)
				cmd.Run()
			}

			var out bytes.Buffer
			cmd = exec.Command(`/bin/sh`, `-c`, `iptables -L OUTPUT -n -v -x|grep `+p+`|awk 'NR==1{print $1","$2}'`)
			cmd.Stdout = &out
			err = cmd.Run()
			checkErr(err)
			sent := out.String()
			out.Reset()
			ss := strings.Split(strings.TrimSpace(sent), `,`)
			if len(ss) != 2 {
				log.Errorf("port:%s has no data!", p)
				s.PacketsSent = 0
				s.BytesSent = 0
			} else {
				sPackets, _ := strconv.ParseUint(ss[0], 10, 64)
				s.PacketsSent = s.PacketsSent + sPackets
				sBytes, _ := strconv.ParseUint(strings.TrimSpace(ss[1]), 10, 64)
				s.BytesSent = s.BytesSent + sBytes
			}

			cmd = exec.Command(`/bin/sh`, `-c`, `iptables -L INPUT -n -v -x|grep `+p+`|awk 'NR==1{print $1","$2}'`)
			cmd.Stdout = &out
			err = cmd.Run()
			checkErr(err)
			recv := out.String()
			out.Reset()
			rs := strings.Split(recv, `,`)
			if len(rs) != 2 {
				log.Errorf("port:%s has no data!", p)
				s.PacketsRecv = 0
				s.BytesRecv = 0
			} else {
				rPackets, _ := strconv.ParseUint(rs[0], 10, 64)
				s.PacketsRecv = s.PacketsRecv + rPackets
				rBytes, _ := strconv.ParseUint(strings.TrimSpace(rs[1]), 10, 64)
				s.BytesRecv = s.BytesRecv + rBytes
			}
		}
	}

	//diskIO
	dcs, err := p.IOCounters()
	if err != nil {
		return nil, err
	}
	s.ReadCount = dcs.ReadCount
	s.WriteCount = dcs.WriteCount
	s.ReadBytes = dcs.ReadBytes
	s.WriteBytes = dcs.WriteBytes
	//tcp
	b, err = ioutil.ReadFile(fmt.Sprintf("/proc/%d/net/tcp", pid))
	if err != nil {
		return nil, err
	}
	s.TcpCount = ParseTcpCount(b)

	if _, ok := last[pid]; !ok {
		last[pid] = &pb.Status{}
	}
	ret := ProcCounterMinus(last[pid], s)
	last[pid] = s

	if ret.CheckTime == 0 {
		ret.CpuUsage = 0
	} else {
		ret.CpuUsage = float64(ret.CuTime+ret.CsTime+ret.UTime+ret.STime) / float64(ret.CheckTime) * cores
	}
	return ret, nil
}

func ParseTcpCount(b []byte) map[string]int32 {
	lines := strings.Split(string(b), "\n")
	p := regexp.MustCompile("\\s+")
	ret := make(map[string]int32)
	for _, l := range lines {
		fields := p.Split(l, -1)
		if len(fields) < 6 {
			continue
		}
		stat, ok := TCPSTATUS[fields[4]]
		if !ok {
			continue
		}
		ret[stat] = ret[stat] + 1
	}
	return ret
}

func ParseCmdline(pid int) (content []byte, u *user.User, ctime int64, err error) {
	fn := fmt.Sprintf("/proc/%d/cmdline", pid)
	content, err = ioutil.ReadFile(fn)
	if err != nil {
		return
	}
	var fi os.FileInfo
	fi, err = os.Stat(fn)
	if err != nil {
		return
	}
	stat := fi.Sys().(*syscall.Stat_t)
	ctime = int64(stat.Ctim.Sec)
	uid := stat.Uid
	u, err = user.LookupId(fmt.Sprintf("%d", uid))
	return
}
